import { Component, ElementRef, HostListener, ViewChild } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import * as d3 from 'd3';
import * as d3Slider from 'd3-simple-slider';
import { BaseComponent } from 'src/api/base_component';
import { Program, ProgramType, Tag, TagType } from '../../../api/dto';

import { LRUCache } from 'lru-cache';
import { Util } from 'src/util';

import {MatDialog, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import { ClockService } from 'src/app/service/clock/clock.service';
import { MatButton } from '@angular/material/button';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.less'],
})
export class HomeComponent extends BaseComponent {
  constructor(private sanitizer: DomSanitizer, private dialog: MatDialog, private clock_service: ClockService) {
    super();
  }

  imgSrc = '';
  tmp = 'ok';
  showCapture = false;

  blobUrlCache = new LRUCache({
    max: 500,
    ttl: 1000 * 60 * 5,
    // // 最大字节数
    // maxSize: 5000,
    // // 每个value的字节数
    // sizeCalculation: (value, key) => {
    //   return 1;
    // },

    dispose: (key: string, value: string) => {
      URL.revokeObjectURL(value);
    },
  });

  is_follow = false;

  program_line: Program[] = [];
  tag_line: Tag[] = [];
  displayedColumns: string[] = ['id', 'program_name', 'title', 'begin_time', 'end_time'];
  program_line_map: Map<number, Program> = new Map();
  tag_line_map: Map<number, Tag> = new Map();

  program_type: ProgramType[] = [];
  program_type_map: Map<string, ProgramType> = new Map();
  tag_type: TagType[] = [];
  tag_type_map: Map<string, TagType> = new Map();

  programInfo = 'info';

  width = 600;
  CANVAS_HEIGHT = 200;

  BLOCK_HEIGHT = 150; // 每个矩形的高度
  date = new Date();
  date_start_time: number = 0; // 时间轴的起始时间
  date_end_time: number = 0; // 时间轴的结束时间

  total_time: number = 0; // 时间轴的总时长

  leftBound = 0;
  rightBound = this.width;

  ngOnInit() {

    this.width = window.innerWidth;
    console.log(window.innerWidth);
    this.updateDate(new Date());
  }

  svg!: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>;
  xScale!: d3.ScaleTime<number, number, never>;
  yScale!: d3.ScaleLinear<number, number, never>;

  svg2!: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>;

  slide_svg!: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>;

  @ViewChild('clock_button') clock_button!: MatButton;

  ngAfterViewInit() {
    // this.width =

    this.svg = d3.select('#line').append('svg').attr('width', this.width).attr('height', this.CANVAS_HEIGHT);
    this.svg2 = d3.select('#line').append('svg').attr('width', this.width).attr('height', this.CANVAS_HEIGHT);
    this.xScale = d3.scaleTime().domain([this.date_start_time, this.date_end_time]).range([0, this.width]);
    this.leftBound = this.xScale(this.date_start_time);
    this.rightBound = this.xScale(this.date_end_time);
    this.yScale = d3.scaleLinear().domain([0, 100]).range([0, this.BLOCK_HEIGHT]);
    this.draw_slide();
  }

  @HostListener('window:resize', ['$event'])
  onResize(event: any) {
    console.log('Window resized');
    this.width = window.innerWidth;
    this.xScale = d3.scaleTime().domain([this.date_start_time, this.date_end_time]).range([0, this.width]);
    this.leftBound = this.xScale(this.date_start_time);
    this.rightBound = this.xScale(this.date_end_time);
    this.draw_line();
    this.draw_line2();
    this.draw_slide();
  }

  async draw_slide() {
    this.slide_svg?.remove();
    this.slide_svg = d3.select('#slider').append('svg').attr('width', this.width).attr('height', 100);
    const slider = (d3Slider as any)
    .sliderBottom()
    .min(this.leftBound)
    .max(this.rightBound)
    .width(400)
    .default([this.leftBound, this.rightBound])
    .ticks(5)
    .step(10)
    .fill('#bfa')
    .on('onchange', (val: any) => {
      this.tmp = val.map(d3.format('.2')).join('-');
      this.leftBound = val[0];
      this.rightBound = val[1];
      // console.log(this.leftBound, this.rightBound)
      this.draw_line();
      this.draw_line2();
    });
    this.slide_svg.append('g').attr('transform', 'translate(30,30)').call(slider)
  }

  getSafeUrl(blobUrl: string) {
    return this.sanitizer.bypassSecurityTrustUrl(blobUrl);
  }

  async draw_line() {
    this.svg?.remove();
    this.svg = d3.select('#line').append('svg').attr('width', this.width).attr('height', this.CANVAS_HEIGHT);
    const leftTime = (this.leftBound / this.width) * this.total_time + this.date_start_time;
    const rightTime = (this.rightBound / this.width) * this.total_time + this.date_start_time;
    console.log(leftTime, rightTime);
    this.xScale = d3.scaleTime().domain([leftTime, rightTime]).range([0, this.width]);
    this.yScale = d3.scaleLinear().domain([0, 100]).range([0, this.BLOCK_HEIGHT]);

    const xAxis = d3.axisBottom(this.xScale);
    this.svg.append('g').attr('transform', `translate(0, ${this.BLOCK_HEIGHT})`).call(xAxis);

    const yAxis = d3.axisLeft(this.yScale);
    this.svg.append('g').call(yAxis);

    this.program_line.forEach((p) => {
      const id = p.id;
      const begin_timestamp = new Date(p.begin_time).getTime();
      const end_timestamp = new Date(p.end_time).getTime();
      if (!(end_timestamp >= leftTime && begin_timestamp <= rightTime)) {
        return;
      }
      const x1 = this.xScale(begin_timestamp);
      const x2 = this.xScale(end_timestamp);

      const rect = this.svg
        .append('rect')
        .attr('x', x1)
        .attr('y', this.yScale(0))
        .attr('width', x2 - x1)
        .attr('height', this.yScale(100))
        .attr('fill', '#' + this.program_type_map.get(p.name)!.color)
        .attr('id', p.id);

      rect
        .on('click', (e: MouseEvent) => {
          this.tmp = String(id);
        })
        .on('mouseover', (e: MouseEvent) => {
          this.programInfo = Program.toString(this.program_line_map.get(id)!);
          rect.style('fill', Util.darkenColor(rect.style('fill'), 0.5));
          rect.style('cursor', 'pointer');
        })
        .on('mousemove', async (e: MouseEvent) => {
          const timestamp = Math.round((e.x / this.width) * (rightTime - leftTime) + leftTime);
          // new Date(timestamp)
          console.log(e.x, e.y, leftTime, timestamp, rightTime);
          if (this.showCapture) {
            const path = await this.api().get_last_capture_path(timestamp);
            if (this.blobUrlCache.has(path)) {
              this.imgSrc = this.blobUrlCache.get(path)!;
            } else {
              this.api()
                .read_file_byte_list(path)
                .then((f) => {
                  const blobUrl = Util.intArrayToBlobUrl(f, 'image/png');
                  this.imgSrc = blobUrl;
                  // this.imgSrc = 'data:image/png;base64,' + f;
                  this.blobUrlCache.set(path, blobUrl);
                });
            }
          }
        })
        .on('mouseout', (e) => {
          rect.style('fill', Util.darkenColor(rect.style('fill'), -0.5));
          rect.style('cursor', 'default');
        });

      // 定义拖拽事件的处理函数
      const dragHandler = d3
        .drag<SVGRectElement, any>()
        .on('start', (e) => {
          console.log(e);
          rect.style('cursor', 'ew-resize');
        })
        .on('drag', (e: any) => {
          const dx = e.dx;
          const x = Number(rect.attr('x'));
          const width = Number(rect.attr('width'));

          const center_x = x + width / 2;
          if (e.x < center_x) {
            // 控制左边界
            const new_x = x + dx;
            const new_width = width - dx;
            let leftBound = this.program_line_map.get(Number(rect.attr('id')) - 1)!.id; // TODO
            if (new_x >= leftBound && new_width >= 0) {
              rect.attr('x', new_x);
              rect.attr('width', new_width);
            }
          } else {
            // 控制右边界
            const new_width = width + dx;
            if (x + new_width <= this.width) {
              rect.attr('width', new_width);
            }
          }
        })
        .on('end', () => {
          // this.drag_direction = '';
          rect.style('cursor', 'default');
        });

      // 调用拖拽事件
      rect.call(dragHandler);
    });
  }

  async draw_line2() {
    this.svg2?.remove();
    this.svg2 = d3.select('#line').append('svg').attr('width', this.width).attr('height', this.CANVAS_HEIGHT);
    const leftTime = (this.leftBound / this.width) * this.total_time + this.date_start_time;
    const rightTime = (this.rightBound / this.width) * this.total_time + this.date_start_time;
    console.log(leftTime, rightTime);
    this.xScale = d3.scaleTime().domain([leftTime, rightTime]).range([0, this.width]);
    this.yScale = d3.scaleLinear().domain([0, 100]).range([0, this.BLOCK_HEIGHT]);

    const xAxis = d3.axisBottom(this.xScale);
    this.svg2.append('g').attr('transform', `translate(0, ${this.BLOCK_HEIGHT})`).call(xAxis);

    const yAxis = d3.axisLeft(this.yScale);
    this.svg2.append('g').call(yAxis);

    this.tag_line.forEach((t) => {
      const id = t.id;
      const begin_timestamp = new Date(t.begin_time).getTime();
      const end_timestamp = new Date(t.end_time).getTime();
      if (!(end_timestamp >= leftTime && begin_timestamp <= rightTime)) {
        return;
      }
      const x1 = this.xScale(begin_timestamp);
      const x2 = this.xScale(end_timestamp);

      const rect = this.svg2
        .append('rect')
        .attr('x', x1)
        .attr('y', this.yScale(0))
        .attr('width', x2 - x1)
        .attr('height', this.yScale(100))
        .attr('fill', '#' + this.tag_type_map.get(t.name)!.color)
        .attr('id', t.id);

      rect
        .on('click', (e: MouseEvent) => {
          this.tmp = String(id);
        })
        .on('mouseover', (e: MouseEvent) => {
          this.programInfo = Tag.toString(this.tag_line_map.get(id)!);
          rect.style('fill', Util.darkenColor(rect.style('fill'), 0.5));
          rect.style('cursor', 'pointer');
        })
        .on('mousemove', async (e: MouseEvent) => {
          const timestamp = Math.round((e.x / this.width) * (rightTime - leftTime) + leftTime);
          // new Date(timestamp)
          console.log(e.x, e.y, leftTime, timestamp, rightTime);
          if (this.showCapture) {
            const path = await this.api().get_last_capture_path(timestamp);
            if (this.blobUrlCache.has(path)) {
              this.imgSrc = this.blobUrlCache.get(path)!;
            } else {
              this.api()
                .read_file_byte_list(path)
                .then((f) => {
                  const blobUrl = Util.intArrayToBlobUrl(f, 'image/png');
                  this.imgSrc = blobUrl;
                  // this.imgSrc = 'data:image/png;base64,' + f;
                  this.blobUrlCache.set(path, blobUrl);
                });
            }
          }
        })
        .on('mouseout', (e) => {
          rect.style('fill', Util.darkenColor(rect.style('fill'), -0.5));
          rect.style('cursor', 'default');
        });

      // 定义拖拽事件的处理函数
      const dragHandler = d3
        .drag<SVGRectElement, any>()
        .on('start', (e) => {
          console.log(e);
          rect.style('cursor', 'ew-resize');
        })
        .on('drag', (e: any) => {
          const dx = e.dx;
          const x = Number(rect.attr('x'));
          const width = Number(rect.attr('width'));

          const center_x = x + width / 2;
          if (e.x < center_x) {
            // 控制左边界
            const new_x = x + dx;
            const new_width = width - dx;
            let leftBound = this.program_line_map.get(Number(rect.attr('id')) - 1)!.id; // TODO
            if (new_x >= leftBound && new_width >= 0) {
              rect.attr('x', new_x);
              rect.attr('width', new_width);
            }
          } else {
            // 控制右边界
            const new_width = width + dx;
            if (x + new_width <= this.width) {
              rect.attr('width', new_width);
            }
          }
        })
        .on('end', () => {
          // this.drag_direction = '';
          rect.style('cursor', 'default');
        });

      // 调用拖拽事件
      rect.call(dragHandler);
    });
  }

  async test() {
    let name = await this.api().test(this.date);
    console.log(name);
  }

  updateDate(date: Date) {
    this.date = new Date(date);

    [this.date_start_time, this.date_end_time] = Util.getDateRange(date);
    this.total_time = this.date_end_time - this.date_start_time;

    // setInterval(async () => {
    setTimeout(async () => {
      console.log('interval');
      // this.program_line = await this.api()?.get_program_line();
      await this.updateProgramLineByDate();
      this.program_line.forEach((p) => {
        this.program_line_map.set(p.id, p);
      });
      await this.updateTagLineByDate();
      this.tag_line.forEach((p) => {
        this.tag_line_map.set(p.id, p);
      });
      this.program_type = await this.api()?.get_program_types();
      this.program_type.forEach((t) => {
        this.program_type_map.set(t.name, t);
      });
      this.tag_type = await this.api()?.get_tag_types();
      this.tag_type.forEach((t) => {
        this.tag_type_map.set(t.name, t);
      });
      this.draw_line();
      this.draw_line2();
    }, 1000);
  }

  async updateProgramLineByDate() {
    this.program_line = await this.api().get_program_line(Util.dateToString(this.date));
  }

  async updateTagLineByDate() {
    this.tag_line = await this.api().get_tag_line(Util.dateToString(this.date));
  }

  async getProgramLine() {
    let l = await this.api().get_program_line();
    console.log(l);
  }

  async getProgramTypes() {
    let l = await this.api().get_program_types();
    console.log(l);
  }

  async getTagLine() {
    let l = await this.api().get_tag_line();
    console.log(l);
  }

  async getTagTypes() {
    let l = await this.api().get_tag_types();
    console.log(l);
  }

  async getFile() {
    let l = await this.api().create_file_dialog();
    console.log(l);
    let path = (l[0] as any).path;
    // let f = await this.api().read_base64_file(path);
    // this.imgSrc = 'data:image/png;base64,' + f;
  }

  jobs: Array<string> = [];
  clock_value = '00:00:10';

  async add_clock() {
    const [hours, minutes, seconds] = this.clock_value.split(':').map(Number);
    let job_id = await this.api().set_clock(hours*3600+minutes*60+seconds);
    // const d = new Date();
    // d.setHours(hours);
    // d.setMinutes(minutes);
    // d.setSeconds(seconds);
    // // this.jobs.push(job_id);
    // this.tmp = job_id;
  }

  hide() {
    this.api().hide();
  }

  openDialog() {
    this.dialog.open(ClockDialog, {
      width: '250px'
    });
  }
}

@Component({
  selector: 'clock-dialog',
  templateUrl: 'clock-dialog.html',
})
export class ClockDialog {}
